/* $ Id$
 * Created on 08.11.2004
 */
package net.sourceforge.ftgl.demos.showroom;

import java.util.LinkedList;

import net.java.games.jogl.GL;
import net.java.games.jogl.GLDrawable;
import net.java.games.jogl.GLEventListener;
import net.java.games.jogl.GLU;
import net.sourceforge.ftgl.demos.showroom.scene.RenderAction;
import net.sourceforge.ftgl.demos.showroom.scene.Scene;
import net.sourceforge.ftgl.glfont.FTFont;
import net.sourceforge.ftgl.math.FTBBox;

/**
 * TODO javadoc
 * 
 * @author joda
 */
public class FTJOGLRenderer implements GLEventListener
{

	/** number of frames rendered */
	public static volatile int framesCounted = 0;

	/** JOGL GL instance*/
	private GL gl = null;

	/** JOGL GL instance*/
	private GLU glu = null;

	public volatile boolean running = false;
	private long lastTime;

	private int w_win = 640, h_win = 480;

	private final Scene scene;
	private final LinkedList actions = new LinkedList();
	

	/**
	 * @param font
	 * @param context
	 * @param drawable
	 */
	public FTJOGLRenderer(Scene scene)
	{
		this.scene = scene;
	}


	public void add(RenderAction a)
	{
		synchronized (this.actions)
		{
			this.actions.add(a);
		}
	}

	
	/**
	 * {@inheritDoc}
	 */
	public void init(GLDrawable drawable)
	{
		this.hasContextChanged(drawable);

		System.err.println("GL_VENDOR: " + gl.glGetString(GL.GL_VENDOR));
		System.err.println("GL_RENDERER: " + gl.glGetString(GL.GL_RENDERER));
		System.err.println("GL_VERSION: " + gl.glGetString(GL.GL_VERSION));

		this.gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
		this.gl.glClearColor(0.13f, 0.17f, 0.32f, 0.0f);
		this.gl.glColor3f(1.0f, 1.0f, 1.0f);

		this.gl.glEnable(GL.GL_CULL_FACE);
		this.gl.glFrontFace(GL.GL_CCW);

		this.gl.glEnable(GL.GL_DEPTH_TEST);

		this.gl.glEnable(GL.GL_POLYGON_OFFSET_LINE);
		this.gl.glPolygonOffset(1.0f, 1.0f); // ????

		setCamera();

		this.lastTime = System.currentTimeMillis();
	}

	/**
	 * {@inheritDoc}
	 */
	public void display(GLDrawable drawable)
	{
		this.hasContextChanged(drawable);
		
		LinkedList l = new LinkedList();
		synchronized (this.actions)
		{
			l.addAll(this.actions);
			this.actions.clear();
		}
		
		final long currentTime = System.currentTimeMillis();
		final long millis = currentTime - this.lastTime;
		this.lastTime = currentTime;
		while (!l.isEmpty())
		{
			RenderAction a = (RenderAction) l.removeFirst();
			a.run(millis);
		}
		
		this.gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
		this.setCamera();

		switch (this.scene.current_ftfont)
		{
			case Scene.FTGL_BITMAP:
			case Scene.FTGL_PIXMAP:
				this.gl.glRasterPos2i(w_win / 2, h_win / 2);
				this.gl.glTranslatef(w_win / 2f, h_win / 2f, 0.0f);
				break;
			case Scene.FTGL_OUTLINE:
			case Scene.FTGL_POLYGON:
			case Scene.FTGL_EXTRUDE:
			case Scene.FTGL_TEXTURE:
				this.gl.glMultMatrixf(this.scene.tb.tbMatrix(new float[16]));
				break;
		}

		this.gl.glPushMatrix();

		predisplay();

		this.gl.glPopMatrix();

		FTJOGLRenderer.framesCounted++;
		// glutSwapBuffers();
	}

	/**
	 * Set up lighting.
	 */
	private void setUpLighting()
	{
		// Set up lighting.
		float[] light1_ambient = { 1.0f, 1.0f, 1.0f, 1.0f };
		float[] light1_diffuse = { 1.0f, 0.9f, 0.9f, 1.0f };
		float[] light1_specular = { 1.0f, 0.7f, 0.7f, 1.0f };
		float[] light1_position = { -1.0f, 1.0f, 1.0f, 0.0f };
		this.gl.glLightfv(GL.GL_LIGHT1, GL.GL_AMBIENT, light1_ambient);
		this.gl.glLightfv(GL.GL_LIGHT1, GL.GL_DIFFUSE, light1_diffuse);
		this.gl.glLightfv(GL.GL_LIGHT1, GL.GL_SPECULAR, light1_specular);
		this.gl.glLightfv(GL.GL_LIGHT1, GL.GL_POSITION, light1_position);
		this.gl.glEnable(GL.GL_LIGHT1);

		float light2_ambient[] = { 0.2f, 0.2f, 0.2f, 1.0f };
		float light2_diffuse[] = { 0.9f, 0.9f, 0.9f, 1.0f };
		float light2_specular[] = { 0.7f, 0.7f, 0.7f, 1.0f };
		float light2_position[] = { 1.0f, -1.0f, -1.0f, 0.0f };
		this.gl.glLightfv(GL.GL_LIGHT2, GL.GL_AMBIENT, light2_ambient);
		this.gl.glLightfv(GL.GL_LIGHT2, GL.GL_DIFFUSE, light2_diffuse);
		this.gl.glLightfv(GL.GL_LIGHT2, GL.GL_SPECULAR, light2_specular);
		this.gl.glLightfv(GL.GL_LIGHT2, GL.GL_POSITION, light2_position);
		// glEnableLIGHT2);

		float front_emission[] = { 0.3f, 0.2f, 0.1f, 0.0f };
		float front_ambient[] = { 0.2f, 0.2f, 0.2f, 0.0f };
		float front_diffuse[] = { 0.95f, 0.95f, 0.8f, 0.0f };
		float front_specular[] = { 0.6f, 0.6f, 0.6f, 0.0f };
		this.gl.glMaterialfv(GL.GL_FRONT, GL.GL_EMISSION, front_emission);
		this.gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, front_ambient);
		this.gl.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, front_diffuse);
		this.gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, front_specular);
		this.gl.glMaterialf(GL.GL_FRONT, GL.GL_SHININESS, 16.0f);
		this.gl.glColor4fv(front_diffuse);

		this.gl.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE, GL.GL_FALSE);
		this.gl.glEnable(GL.GL_CULL_FACE);
		this.gl.glColorMaterial(GL.GL_FRONT, GL.GL_DIFFUSE);
		this.gl.glEnable(GL.GL_COLOR_MATERIAL);

		this.gl.glEnable(GL.GL_LIGHTING);
		this.gl.glShadeModel(GL.GL_SMOOTH);
	}

	/**
	 * Verifies if the GL or GLU context changed.
	 * @param drawable The actual drawable.
	 */
	private final void hasContextChanged(GLDrawable drawable)
	{
		if (drawable.getGL() != this.gl || drawable.getGLU() != this.glu)
		{
			this.gl  = drawable.getGL();
			this.glu = drawable.getGLU();
			this.scene.setGLGLU(this.gl, this.glu);
			System.out.println("GL or GLU context changed!");
		}
	}


	/**
	 * Draws the bounding boxes around the text.
	 */
	void renderFontmetrics()
	{
		
		final FTFont current = this.scene.getCurrentFTFont();
		final FTBBox box = current.getBBox(this.scene.displayString);

		// Draw the bounding box
		this.gl.glDisable(GL.GL_LIGHTING);
		this.gl.glDisable(GL.GL_TEXTURE_2D);
		this.gl.glEnable(GL.GL_LINE_SMOOTH);
		this.gl.glEnable(GL.GL_BLEND);
		this.gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE); // ONE_MINUS_SRC_ALPHA

		this.gl.glColor3f(0.0f, 1.0f, 0.0f);
		// Draw the front face
		this.gl.glBegin(GL.GL_LINE_LOOP);
		this.gl.glVertex3f(box.lowerX, box.lowerY, box.lowerZ);
		this.gl.glVertex3f(box.lowerX, box.upperY, box.lowerZ);
		this.gl.glVertex3f(box.upperX, box.upperY, box.lowerZ);
		this.gl.glVertex3f(box.upperX, box.lowerY, box.lowerZ);
		this.gl.glEnd();
		// Draw the back face
		if (box.lowerZ != box.upperZ)
		{
			this.gl.glBegin(GL.GL_LINE_LOOP);
			this.gl.glVertex3f(box.lowerX, box.lowerY, box.upperZ);
			this.gl.glVertex3f(box.lowerX, box.upperY, box.upperZ);
			this.gl.glVertex3f(box.upperX, box.upperY, box.upperZ);
			this.gl.glVertex3f(box.upperX, box.lowerY, box.upperZ);
			this.gl.glEnd();
			// Join the faces
			this.gl.glBegin(GL.GL_LINES);
			this.gl.glVertex3f(box.lowerX, box.lowerY, box.lowerZ);
			this.gl.glVertex3f(box.lowerX, box.lowerY, box.upperZ);

			this.gl.glVertex3f(box.lowerX, box.upperY, box.lowerZ);
			this.gl.glVertex3f(box.lowerX, box.upperY, box.upperZ);

			this.gl.glVertex3f(box.upperX, box.upperY, box.lowerZ);
			this.gl.glVertex3f(box.upperX, box.upperY, box.upperZ);

			this.gl.glVertex3f(box.upperX, box.lowerY, box.lowerZ);
			this.gl.glVertex3f(box.upperX, box.lowerY, box.upperZ);
			this.gl.glEnd();
		}

		// Draw the baseline, Ascender and Descender
		this.gl.glBegin(GL.GL_LINES);
		this.gl.glColor3f(0.0f, 0.0f, 1.0f);
		this.gl.glVertex3f(0.0f, 0.0f, 0.0f);
		this.gl.glVertex3f(current.metrics(this.scene.displayString).advanceX, 0.0f, 0.0f);
		this.gl.glVertex3f(0.0f, current.ascender(), 0.0f);
		assert (current.ascender()>=current.descender()):"Ascender is not greater or equal to descender.";
		this.gl.glVertex3f(0.0f, current.descender(), 0.0f);

		this.gl.glEnd();

		// Draw the origin
		this.gl.glColor3f(1.0f, 0.0f, 0.0f);
		this.gl.glPointSize(5.0f);
		this.gl.glBegin(GL.GL_POINTS);
		this.gl.glVertex3f(0.0f, 0.0f, 0.0f);
		this.gl.glEnd();

//		this.gl.glLineWidth(3f);
//		this.gl.glBegin(GL.GL_LINES);
//		this.gl.glColor3f(1f,0f,0f);
//		final float LENGTH = 28f;
//		this.gl.glVertex3f(LENGTH,0f,0f);
//		this.gl.glVertex3f(0f,0f,0f);
//		this.gl.glColor3f(0f,1f,0f);
//		this.gl.glVertex3f(0f,LENGTH,0f);
//		this.gl.glVertex3f(0f,0f,0f);
//		this.gl.glColor3f(0f,0f,1f);
//		this.gl.glVertex3f(0f,0f,LENGTH);
//		this.gl.glVertex3f(0f,0f,0f);
//		this.gl.glEnd();

		this.gl.glLineWidth(1f);
	}

	/**
	 * 
	 */
	void renderFontInfo()
	{
		this.gl.glMatrixMode(GL.GL_PROJECTION);
		this.gl.glLoadIdentity();
		this.glu.gluOrtho2D(0, w_win, 0, h_win);
		this.gl.glMatrixMode(GL.GL_MODELVIEW);
		this.gl.glLoadIdentity();

		// draw mode
		this.gl.glColor3f(1.0f, 1.0f, 1.0f);
		this.gl.glRasterPos2f(20.0f, h_win - (20.0f + this.scene.infoFont.ascender()));

		switch (this.scene.mode)
		{
			case Scene.EDITING:
				this.scene.infoFont.render("Edit Mode");
				break;
			case Scene.INTERACTIVE:
				break;
		}

		// draw font type
		this.gl.glRasterPos2i(20, 20);
		switch (this.scene.current_ftfont)
		{
			case Scene.FTGL_BITMAP:
				this.scene.infoFont.render("Bitmap Font");
				break;
			case Scene.FTGL_PIXMAP:
				this.scene.infoFont.render("Pixmap Font");
				break;
			case Scene.FTGL_OUTLINE:
				this.scene.infoFont.render("Outline Font");
				break;
			case Scene.FTGL_POLYGON:
				this.scene.infoFont.render("Polygon Font");
				break;
			case Scene.FTGL_EXTRUDE:
				this.scene.infoFont.render("Extruded Font");
				break;
			case Scene.FTGL_TEXTURE:
				this.scene.infoFont.render("Texture Font");
				break;
		}
		this.gl.glRasterPos2f(20.0f, 20.0f + this.scene.infoFont.ascender() - this.scene.infoFont.descender());
		this.scene.infoFont.render(this.scene.displayString);
	}

	/**
	 * 
	 */
	public void predisplay()
	{
		switch (this.scene.current_ftfont)
		{
			case Scene.FTGL_BITMAP:
			case Scene.FTGL_PIXMAP:
			case Scene.FTGL_OUTLINE:
				break;
			case Scene.FTGL_POLYGON:
				this.gl.glDisable(GL.GL_BLEND);
				setUpLighting();
				break;
			case Scene.FTGL_EXTRUDE:
				this.gl.glEnable(GL.GL_DEPTH_TEST);
				this.gl.glDisable(GL.GL_BLEND);
				setUpLighting();
				break;
			case Scene.FTGL_TEXTURE:
				this.gl.glEnable(GL.GL_TEXTURE_2D);
				this.gl.glDisable(GL.GL_DEPTH_TEST);
				setUpLighting();
				this.gl.glNormal3f(0.0f, 0.0f, 1.0f);
				break;

		}

		FTFont current = this.scene.getCurrentFTFont();
		if (current != null)
		{
			FTBBox box = current.getBBox(this.scene.displayString);
			this.gl.glPushMatrix();
			this.gl.glTranslatef(-box.getWidth() * 0.5f, -box.getHeight() * 0.5f, 0f);

			this.renderCarat();

			this.gl.glColor4fv(Scene.COLORS[this.scene.current_color]);
			// If you do want to switch the color of bitmaps rendered with glBitmap,
			// you will need to explicitly call glRasterPos3f (or its ilk) to lock
			// in a changed current color.
			this.gl.glRasterPos3f(0f,0f,0f);

			this.gl.glPushMatrix();
			current.render(this.scene.displayString);
			this.gl.glPopMatrix();

			this.renderFontmetrics();
			this.gl.glPopMatrix();
		}
		this.renderFontInfo();
	}

	private void renderCarat()
	{
		if (this.scene.carat >= 0)
		{
			final FTFont current = this.scene.getCurrentFTFont();
			float caratPos = current.metrics(this.scene.displayString.substring(0,this.scene.carat)).advanceX;
			this.gl.glPushAttrib(GL.GL_LIGHTING_BIT | GL.GL_LINE_BIT | GL.GL_TEXTURE_BIT);
			this.gl.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
			this.gl.glLineWidth(12f);
			this.gl.glDisable(GL.GL_LIGHTING);
			this.gl.glDisable(GL.GL_TEXTURE_2D);
			this.gl.glBegin(GL.GL_LINES);
			this.gl.glVertex3f(caratPos, current.ascender(), 0.0f);
			this.gl.glVertex3f(caratPos, current.descender(), 0.0f);
			this.gl.glEnd();
			this.gl.glPopAttrib();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public void displayChanged(GLDrawable drawable, boolean modeChanged, boolean deviceChanged)
	{
		this.hasContextChanged(drawable);
	}

	/**
	 * {@inheritDoc}
	 */
	public void reshape(GLDrawable drawable, int x, int y, int width, int height)
	{
		this.hasContextChanged(drawable);

		this.gl.glMatrixMode(GL.GL_MODELVIEW);
		this.gl.glViewport(0, 0, width, height);
		this.gl.glLoadIdentity();

		this.w_win = width;
		this.h_win = height;
		setCamera();

		this.scene.tb.tbReshape(this.w_win, this.h_win);
	}

	void setCamera()
	{
		switch (this.scene.current_ftfont)
		{
			case Scene.FTGL_BITMAP:
			case Scene.FTGL_PIXMAP:
				this.gl.glMatrixMode(GL.GL_PROJECTION);
				this.gl.glLoadIdentity();
				this.glu.gluOrtho2D(0, w_win, 0, h_win);
				this.gl.glMatrixMode(GL.GL_MODELVIEW);
				this.gl.glLoadIdentity();
				break;
			case Scene.FTGL_OUTLINE:
			case Scene.FTGL_POLYGON:
			case Scene.FTGL_EXTRUDE:
			case Scene.FTGL_TEXTURE:
				this.gl.glMatrixMode(GL.GL_PROJECTION);
				this.gl.glLoadIdentity();
				this.glu.gluPerspective(90, (float)w_win / h_win, 1, 1000);
				this.gl.glMatrixMode(GL.GL_MODELVIEW);
				this.gl.glLoadIdentity();
				this.glu.gluLookAt(0.0, 0.0, h_win / this.scene.zoom, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
				break;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public String toString()
	{
		StringBuffer sb = new StringBuffer();
		switch (this.scene.mode)
		{
			case Scene.EDITING:
				sb.append("\tMode: Edit Mode\n");
				break;
			case Scene.INTERACTIVE:
				sb.append("\tMode: Interactive Mode\n");
				break;
		}
		sb.append("\tType: ");
		switch (this.scene.current_ftfont)
		{
			case Scene.FTGL_BITMAP:
				sb.append("Bitmap Font\n");
				break;
			case Scene.FTGL_PIXMAP:
				sb.append("Pixmap Font\n");
				break;
			case Scene.FTGL_OUTLINE:
				sb.append("Outline Font\n");
				break;
			case Scene.FTGL_POLYGON:
				sb.append("Polygon Font\n");
				break;
			case Scene.FTGL_EXTRUDE:
				sb.append("Extruded Font\n");
				break;
			case Scene.FTGL_TEXTURE:
				sb.append("Texture Font\n");
				break;
		}

//		if (this.font instanceof OpenType) sb.append(" (OpenType) ");

		sb.append("\tFontfile: ");
		sb.append(this.scene.displayString);
		sb.append("\n");
		return sb.toString();
	}

}